import axios, { AxiosInstance } from 'axios'
import { Agent } from 'https'
import axiosRetry from 'axios-retry'
import { homedir } from 'os'
const httpsAgent = new Agent({
    rejectUnauthorized: false,
    keepAlive: true,
    timeout: 5000
})
function parseCookieString(cookieString: string | string[] | undefined): Record<string, any> {
    if (!cookieString) return {}
    if (Array.isArray(cookieString)) {
        return cookieString.map(cookie => parseCookieString(cookie))
    }
    const cookieParts = cookieString.split(';').map(part => part.trim());
    const cookieObject: Record<string, any> = {};
    for (const part of cookieParts) {
        const keyValue = part.split('=');
        if (keyValue.length === 2) {
            cookieObject[keyValue[0]] = keyValue[1];
        } else if (keyValue.length === 1 && ['httponly', 'secure'].includes(keyValue[0].toLowerCase())) {
            cookieObject[keyValue[0].toLowerCase()] = true;
        }
    }
    return cookieObject;
}

let instance: AxiosInstance | null = null;
export function createAxios() {
    if (instance) return instance;
    instance = axios.create()
    instance.interceptors.response.use((v) => {
        if (v.config.url) {
            const url = new URL(v.config.url);
            console.log(url.hostname)
        }
        const cookie = parseCookieString(v.headers['set-cookie'])
        v.config.httpsAgent = httpsAgent;
        v.config.timeout = 5000;
        v.config.withCredentials = true;
        console.log({ cookie })
        return v;
    }, e => {
        if (axios.isCancel(e)) {
            console.log(`cancel ${e.message}`)
        }
        const { cause, config, request, ...args } = e;
        console.log(args)
        return Promise.reject(e)
    })
    axiosRetry(instance, {
        retries: 3,
        retryDelay: retryCount => retryCount * 1000,
        retryCondition: error => error.code === 'ECONNRESET'
    })
    return instance;
}


import { Browser, Frame, HTTPRequest, HTTPResponse, launch, Page, Target, WebWorker } from 'puppeteer';
import { ProxyBrowser, useEntityManager } from './entities'
import { v4 } from 'uuid'
import { ProxyBrowserPage, ProxyPageEvent, ProxyResource } from './entities/browser'
import { dirname, extname, join, posix } from 'path'
function getId(target: any) {
    if (!target) return;
    return target.id;
}
function setId(target: any, id: string) {
    if (!target) return;
    target.id = id;
}
const browserMap: Map<string, Browser> = new Map();
export const createBrowser = async (clientId: string) => {
    // 一个浏览器 一个routing
    const browser = await launch({
        headless: false,
        defaultViewport: {
            width: 1920,
            height: 1080,
            deviceScaleFactor: 1
        },
        args: ['--no-sandbox', '--disable-setuid-sandbox', '--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"']
    });
    const uuid = v4();
    await useEntityManager(async m => {
        const entity = m.create(ProxyBrowser, {
            uuid,
            client_id: clientId
        });
        await m.save(ProxyBrowser, entity);
        browserMap.set(uuid, browser)
    });
    setId(browser, uuid)
    browser.on('*', async (...args: any) => {
        if (args && args.length > 0) {
            const [type, payload] = args;
            const target = payload as Target
            switch (type) {
                case 'targetcreated':
                    targetcreated(clientId, uuid, target)
                    break;
                case 'targetdiscovered':
                    break;
                case 'targetchanged':
                    targetchanged(clientId, uuid, target)
                    break;
                case 'targetdestroyed':
                    targetdestroyed(clientId, uuid, target)
                    break;
                default:
                    break;
            }
        }
    })
    return uuid;
}

export async function targetdestroyed(clientId: string, browserId: string, target: Target) {
    const type = target.type()
    if (type === 'page') {
        const page = await target.page()
        if (!page) return;
        const pageId = getId(page)
        if (pageId) {
            pageMap.delete(pageId)
        }
    }
}

export async function targetcreated(clientId: string, browserId: string, target: Target) {
    const type = target.type()
    if (type === 'page') {
        const page = await target.page()
        if (!page) return;
        const pageId = getId(page)
        if (pageId) return;
        const uuid = v4()
        setId(page, uuid)
        await bindPage(page, browserId, clientId)
    }
}

export async function targetchanged(clientId: string, browserId: string, target: Target) {
    const type = target.type()
    if (type === 'page') {
        const page = await target.page()
        if (!page) return;
        const pageId = getId(page)
        if (pageId) return;
        const uuid = v4()
        setId(page, uuid)
        await bindPage(page, browserId, clientId)
    }
}
export async function bindPage(page: Page, browserId: string, clientId: string) {
    const pageId = getId(page)
    pageMap.set(pageId, page)
    await useEntityManager(async m => {
        const page = m.create(ProxyBrowserPage, {
            uuid: pageId,
            browser_id: browserId,
            client_id: clientId
        })
        await m.save(ProxyBrowserPage, page)
    });
    page.on('*', (...args: any[]) => {
        if (args.length > 0) {
            const [type, payload, ...others] = args;
            switch (type) {
                case 'requestfinished':
                    break;
                case 'response':
                    break;
                case 'request':
                    break;
                case 'load':
                    break;
                case 'workercreated':
                    break;
                case 'workerdestroyed':
                    break;
                case 'domcontentloaded':
                    break;
                case 'framenavigated':
                    break;
                case 'requestservedfromcache':
                    break;
                case 'pageerror':
                    break;
                default:
                    break;
            }
        }
    })
}
const pageMap: Map<string, Page> = new Map();
export const createBrowserPage = async (browserId: string, clientId: string) => {
    const browser = browserMap.get(browserId);
    if (!browser) {
        await useEntityManager(async m => {
            await m.delete(ProxyBrowser, browserId)
        })
        throw new Error(`browser not found`)
    }
    const page = await browser.newPage()
    const uuid = v4()
    setId(page, uuid)
    await bindPage(page, browserId, clientId)
    await page.goto('https://www.kuaishou.com/new-reco')
    return uuid;
}
export interface Payload<T> {
    clientId: string;
    browserId: string;
    pageId: string;
    type: string;
    payload: T;
}
import { ensureDir, writeFile } from 'fs-extra'
async function response(options: Payload<HTTPResponse>) {
    await useEntityManager(async m => {
        const uuid = v4()
        const response = options.payload
        const resourceType = response.request().resourceType()
        if (response.ok()) {
            if (['document', 'script', 'script', 'font', 'image', 'stylesheet'].includes(resourceType)) {
                const uuid = v4()
                const url = new URL(response.url())
                const root = homedir()
                let pathname = url.pathname
                if (!pathname.startsWith('/')) {
                    return;
                }
                const ext = extname(pathname)
                if (!ext) {
                    if (pathname.endsWith('/')) {
                        pathname += 'index.html'
                    } else {
                        pathname += '/index.html'
                    }
                }
                const path = posix.join(url.hostname, pathname)
                if (!path) {
                    throw new Error(`path is null`)
                }
                const filePath = join(root, 'attachments/proxy', path)
                await ensureDir(dirname(filePath))
                console.log(filePath)
                await writeFile(filePath, await response.buffer().catch(e => {
                    console.log({ resourceType, url: response.url() })
                    return Buffer.from([])
                }))
                return;
            }
        }
        // let body: any = undefined;
        // if (response.ok()) {
        //     body = await response.buffer().catch(e => ``)
        //     if (!body) {
        //         console.log(`body is empty`)
        //     }
        // }
        // const event = m.create(ProxyPageEvent, {
        //     uuid: uuid,
        //     client_id: options.clientId,
        //     browser_id: options.browserId,
        //     page_id: options.pageId,
        //     type: options.type,
        //     value: {
        //         headers: response.headers(),
        //         url: response.url(),
        //         remoteAddress: response.remoteAddress(),
        //         ok: response.ok(),
        //         status: response.status(),
        //         statusText: response.statusText(),
        //         resourceType,
        //         fromCache: response.fromCache(),
        //         fromServiceWorker: response.fromServiceWorker(),
        //         body: Buffer.from(body || '')
        //     }
        // })
        // await m.save(ProxyPageEvent, event)
    })
}
async function pageerror(options: Payload<Error>) {
    await useEntityManager(async m => {
        const uuid = v4()
        const frame = options.payload
        const event = m.create(ProxyPageEvent, {
            uuid: uuid,
            client_id: options.clientId,
            browser_id: options.browserId,
            page_id: options.pageId,
            type: options.type,
            value: {
                name: frame.name,
                message: frame.message,
                stack: frame.stack
            }
        })
        await m.save(ProxyPageEvent, event)
    })
}
async function framenavigated(options: Payload<Frame>) {
    await useEntityManager(async m => {
        const uuid = v4()
        const frame = options.payload
        const element = await frame.frameElement();
        const nameOrId = await element?.evaluate(frame => frame.name ?? frame.id);
        const content = await frame.content()
        const event = m.create(ProxyPageEvent, {
            uuid: uuid,
            client_id: options.clientId,
            browser_id: options.browserId,
            page_id: options.pageId,
            type: options.type,
            value: {
                name: nameOrId,
                url: frame.url(),
                content: Buffer.from(content),
                detached: frame.detached,
                title: frame.title()
            }
        })
        await m.save(ProxyPageEvent, event)
    })
}
async function workercreated(options: Payload<WebWorker>) {
    await useEntityManager(async m => {
        const uuid = v4()
        const worker = options.payload
        const event = m.create(ProxyPageEvent, {
            uuid: uuid,
            client_id: options.clientId,
            browser_id: options.browserId,
            page_id: options.pageId,
            type: options.type,
            value: {
                url: worker.url()
            }
        })
        await m.save(ProxyPageEvent, event)
    })
}

async function request(options: {
    clientId: string,
    browserId: string,
    pageId: string,
    type: string,
    response: HTTPRequest
}) {
    await useEntityManager(async m => {
        const uuid = v4()
        const request = options.response
        const value = responseToJson(request)
        const event = m.create(ProxyPageEvent, {
            uuid: uuid,
            client_id: options.clientId,
            browser_id: options.browserId,
            page_id: options.pageId,
            type: options.type,
            value: value
        })
        await m.save(ProxyPageEvent, event)
    })
}
async function requestfinished(options: {
    clientId: string, browserId: string, pageId: string, type: string, response: HTTPRequest
}) {
    await useEntityManager(async m => {
        const uuid = v4()
        const request = options.response
        const value = responseToJson(request)
        const event = m.create(ProxyPageEvent, {
            uuid: uuid,
            client_id: options.clientId,
            browser_id: options.browserId,
            page_id: options.pageId,
            type: options.type,
            value: value
        })
        await m.save(ProxyPageEvent, event)
    })
}

function responseToJson(request?: HTTPRequest) {
    if (!request) return;
    return {
        url: request.url(),
        resourceType: request.resourceType(),
        headers: request.headers(),
        method: request.method(),
        hasPostData: request.hasPostData(),
        fetchPostData: request.fetchPostData(),
        isNavigationRequest: request.isNavigationRequest(),
        postData: request.postData(),
    }
}